﻿using RockBus.DataContracts;
using RockBus.Logging.Interfaces;
using RockBus.Messages.Interfaces;
using RockBus.PerformanceCounters.Interfaces;
using RockBus.Pipeline.Interfaces;
using RockBus.Routing.Interfaces;
using System;
using System.Reflection;
using System.ServiceModel.Channels;

namespace RockBus.Routing
{
    public class PipelineManager : IPipelineManager
    {
        public PipelineManager(
            ILogger logger,
            IPerformanceCounterFactory performanceCounterFactory,
            IMessageFactory messageFactory,
            IQueueManager queueManager,
            ISubscriptionConfigurationManager subscriptionConfigurationManager,
            IPipelineFactory pipelineFactory)
        {
            this.Logger = logger;
            this.PerformanceCounterFactory = performanceCounterFactory;
            this.MessageFactory = messageFactory;
            this.QueueManager = queueManager;
            this.PipelineFactory = pipelineFactory;

            subscriptionConfigurationManager.OnSubscriptionConfigurationChanged += subscriptionConfigurationManager_OnSubscriptionConfigurationChanged;
        }

        private ILogger Logger { get; set; }

        private IPerformanceCounterFactory PerformanceCounterFactory { get; set; }

        private IMessageFactory MessageFactory { get; set; }

        private IQueueManager QueueManager { get; set; }

        private IPipelineFactory PipelineFactory { get; set; }

        private void subscriptionConfigurationManager_OnSubscriptionConfigurationChanged(object sender, SubscriptionConfigurationChangedEventArgs e)
        {
            try
            {
                this.PipelineFactory.Reinitialize(e.SubscriberConfiguration, e.PublisherConfiguration);
            }
            catch (Exception exc)
            {
                this.Logger.LogError(MethodBase.GetCurrentMethod(), exc.ToString());
            }
        }

        public Message ProcessInboundRequest(Message message, ReceivePortDetail receivePortDetail)
        {
            var pipeline = this.PipelineFactory.CreateInboundRequestPipeline(receivePortDetail);
            return this.Execute(pipeline, message);
        }

        public Message ProcessOutboundResponse(Message message, ReceivePortDetail receivePortDetail)
        {
            var pipeline = this.PipelineFactory.CreateOutboundResponsePipeline(receivePortDetail);
            return this.Execute(pipeline, message);
        }

        public Message ProcessOutboundRequest(Message message, SubscriptionDetail subscriptionDetail)
        {
            var pipeline = this.PipelineFactory.CreateOutboundRequestPipeline(subscriptionDetail);
            return this.Execute(pipeline, message);
        }

        public Message ProcessInboundResponse(Message message, SubscriptionDetail subscriptionDetail)
        {
            var pipeline = this.PipelineFactory.CreateInboundResponsePipeline(subscriptionDetail);
            return this.Execute(pipeline, message);
        }

        private Message Execute(IPipeline pipeline, Message message)
        {
            if (pipeline == null)
            {
                this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "No pipeline specified for Message with action {0}", message.Headers.Action);
                return message;
            }

            if (message == null)
            {
                throw new InvalidOperationException("No input message specified.");
            }

            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Executing pipeline {0} for message with action {1}", pipeline.Id, message.Headers.Action);
            var pipelineContext = this.PipelineFactory.CreatePipelineContext(message);
            var outputMessage = pipeline.Execute(message, pipelineContext);

            return outputMessage;
        }
    }
}